package com.rainyun.rc.util;

import com.google.common.io.ByteStreams;
import com.google.common.primitives.Bytes;

import java.io.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**jar工具类*/
public class RainJarTool {

	/**替换jar的class的常量名称
	 * @param jar jar文件
	 * @param jarpath jar内特定路径
	 * @param toReplace 需要替换的字符串,格式:<{"开始标识","结束标识"},"替换内容">*/
	public static void replaceConstInJar(File jar, String jarpath,
					 Map<String[],String> toReplace) throws IOException {
		File par = jar.getParentFile();
		File root = new File(par, "rc_tmp_compatify");
		root.mkdir();
		try {
			Map<String,List<Byte>> contents = RainJarTool.readJarFile(jar,jarpath);

			for(Map.Entry<String, List<Byte>> en:contents.entrySet()){//class字节替换
				String path = en.getKey();
				File file = new File(root,path);
				RainFile.newFile(file);
				FileOutputStream os = new FileOutputStream(file);
				Byte[] byo = en.getValue().toArray(new Byte[0]);
				byte[] by = new byte[byo.length];
				for(int i=0;i<by.length;i++)by[i]=byo[i];

				for (Map.Entry<String[], String> sen:toReplace.entrySet()){
					byte[] start = sen.getKey()[0].getBytes("UTF-8");
					byte[] end = new byte[0];
					if(sen.getKey().length>1) {
						end = sen.getKey()[1].getBytes("UTF-8");
					}
					by = replaceConstPool(by,start,end,sen.getValue().getBytes("UTF-8"));
				}
				os.write(by);
				os.close();
			}
			replaceToJar(jar,root,jarpath);
		}catch (Exception e){
			throw e;
		}finally {
			RainFile.delFile(root);
		}
	}

	/**以某文件替换当前jar内对应路径的文件
	 * @param jar jar文件
	 * @param root 某文件的父级目录
	 * @param jarpath jar内路径*/
	public static void replaceToJar(File jar,File root, String jarpath) throws IOException {
		int spi = jarpath.indexOf("/");//取第一个文件夹
		if(spi!=-1)jarpath = jarpath.substring(0,spi);
		String cmd = "jar uf " + jar.getAbsolutePath()+" "+jarpath;
		Process pro = null;
		try {
			pro = Runtime.getRuntime().exec(cmd,null,root);
			pro.waitFor(5,TimeUnit.SECONDS);
			if(pro.isAlive()){//超时
				pro.destroyForcibly();
				Thread.sleep(1000);
				throw new IOException("Time for handle over 3s was too long");
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally {
			//删除老文件和临时文件
			File tf = new File(jarpath);
			RainFile.delFile(tf);
			File par = jar.getParentFile();
			if(par!=null&&par.exists())
				for(File f:par.listFiles()){
					if(f.isFile()&&f.getName().startsWith("jartmp"))
						f.deleteOnExit();
				}
		}
	}

	/**读取jar的特定路径的所有文件的字节*/
	public static Map<String,List<Byte>> readJarFile(File jar, String jarpath) throws IOException {
		JarFile jarFile = new JarFile(jar);
		Map<String,List<Byte>> im = new HashMap<>();
		try {
			Enumeration<JarEntry> jens = jarFile.entries();
			while (jens.hasMoreElements()){
				JarEntry je = jens.nextElement();
				String name = je.getName();
				if(name.startsWith(jarpath))
					if(name.lastIndexOf(".")>name.lastIndexOf("/"))
					{
						InputStream in = jarFile.getInputStream(je);
						byte[] ba = ByteStreams.toByteArray(in);
						List<Byte> bl = Bytes.asList(ba);
						im.put(name,bl);
						in.close();
					}
			}
		}catch (IOException e){
			throw e;
		}finally {
			jarFile.close();
		}
		return im;
	}


	/**
	 * 魔改class字节常量池
	 * @param bytes 字节
	 * @param start 起始字节
	 * @param end 结束字节
	 * @param ReplaceTo 替换字节(带有起始和结尾的字节部分会被替换)*/
	public static byte[] replaceConstPool(byte[] bytes, byte[] start, byte[] end, byte[] ReplaceTo)
	{
		byte[] newby = new byte[0];

		int b = 0;
		int e = 0;
		while (true){
			b = indexOf(bytes,start,e);
			if(b!=-1){
				int etmp = indexOf(bytes,end,b+start.length);
				if(etmp!=-1){
					newby = Bytes.concat(newby,Arrays.copyOfRange(bytes,e,b),ReplaceTo);
					e=etmp+end.length;
					byte[] tag = {1};//常量起始标识
					int tagi = lastIndexOf(newby,tag,newby.length-ReplaceTo.length);
					if(tagi!=-1)newby[tagi+2]+=ReplaceTo.length-(e-b);//修改常量空间占用
//					String str = "";
//					String str2 = "";
//					byte[] byy = Arrays.copyOfRange(newby, newby.length-100, newby.length);
//					System.out.println(new String(byy));
//					for(byte bb:byy)
//					{
//						str2+=bb+".";
//						str+=Integer.toHexString(bb)+".";
//					}
//					System.out.println(str);
//					System.out.println(str2);
				}else{
					newby = Bytes.concat(newby,Arrays.copyOfRange(bytes,e,b+start.length));
					e=b+start.length;
				}
			}else{
				newby = Bytes.concat(newby,Arrays.copyOfRange(bytes,e,bytes.length));
				break;
			}
		}
		return newby;
	}

	/**字节indexOf,参照String源码设计*/
	public static int indexOf(byte[] bytes,byte[] find, int fromIndex) {
		return indexOf(bytes, 0, bytes.length,
				find, 0, find.length, fromIndex);
	}

	static int indexOf(byte[] source, int sourceOffset, int sourceCount,
					   byte[] target, int targetOffset, int targetCount,
					   int fromIndex) {

		if (fromIndex >= sourceCount) {
			return (targetCount == 0 ? sourceCount : -1);
		}
		if (fromIndex < 0) {
			fromIndex = 0;
		}
		if (targetCount == 0) {
			return fromIndex;
		}
		byte first = target[targetOffset];
		int max = sourceOffset + (sourceCount - targetCount);

		for (int i = sourceOffset + fromIndex; i <= max; i++) {
			if (source[i] != first) {
				while (++i <= max && source[i] != first);
			}

			if (i <= max) {
				int j = i + 1;
				int end = j + targetCount - 1;
				for (int k = targetOffset + 1; j < end && source[j]
						== target[k]; j++, k++);

				if (j == end) {
					return i - sourceOffset;
				}
			}
		}
		return -1;
	}

	/**字节lastIndexOf,参照String源码设计*/
	public static int lastIndexOf(byte[] bytes,byte[] find, int fromIndex) {
		return lastIndexOf(bytes, 0, bytes.length,
				find, 0, find.length, fromIndex);
	}

	static int lastIndexOf(byte[] source, int sourceOffset, int sourceCount,
						   byte[] target, int targetOffset, int targetCount,
						   int fromIndex) {
		/*
		 * Check arguments; return immediately where possible. For
		 * consistency, don't check for null str.
		 */
		int rightIndex = sourceCount - targetCount;
		if (fromIndex < 0) {
			return -1;
		}
		if (fromIndex > rightIndex) {
			fromIndex = rightIndex;
		}
		/* Empty string always matches. */
		if (targetCount == 0) {
			return fromIndex;
		}

		int strLastIndex = targetOffset + targetCount - 1;
		byte strLastChar = target[strLastIndex];
		int min = sourceOffset + targetCount - 1;
		int i = min + fromIndex;

		startSearchForLastChar:
		while (true) {
			while (i >= min && source[i] != strLastChar) {
				i--;
			}
			if (i < min) {
				return -1;
			}
			int j = i - 1;
			int start = j - (targetCount - 1);
			int k = strLastIndex - 1;

			while (j > start) {
				if (source[j--] != target[k--]) {
					i--;
					continue startSearchForLastChar;
				}
			}
			return start - sourceOffset + 1;
		}
	}

}
